篇首语:本文由编程笔记#小编为大家整理,主要介绍了重学C++:笔记C++基础容器&C++指针引用相关的知识,希望对你有一定的参考价值。
off-by-one error数组下标
off-by-one error (差一错误)
数组中通过左闭右开的方式可以避免上述错误,如:
for (int i &#61; 0; i <10 ; i&#43;&#43;)
cout <
数组设计的原则
从0开始&#xff0c;使用非对称空间&#xff1a;
让下界&#xff08;左侧&#xff09;可以取到值&#xff0c;让上界&#xff08;右侧&#xff09;取不到值&#xff1b;
好处&#xff1a;
二维数组设计的tips&#xff08;循环时尽可能要满足空间局限性&#xff09;
使用前的准备&#xff08;引入头文件和namespace&#xff09;
#include
using namespace std;
相关方法
vector
//在尾部插入元素
vec.push_back(4);
//在中间进行元素插入
vec.insert(vec.end()-1, 4); //在尾部前一个位置插入4
//删除尾部元素
vec.pop_back();
//删除中间元素
vec.erase(vec.end()-1); //删除尾部前一个位置的元素
//获取当前容量
vec.capacity();
//获取已经存储的元素个数
vec.size();
字符串变量
Unicode编码
Unicode编码&#xff1a;最初的目的是把世界上的文字都映射到一套字符空间中
编码错误的根本原因在于编码方式和解码方式的不统一
Windows的文件可能有BOM&#xff08;byte order mark&#xff09;&#xff0c;如果要在其他平台使用&#xff0c;可以去掉BOM
字符串的指针表示
指针所指的区域能否改变取决于指针指向的那块区域是否为可变的&#xff0c;如若指向的为常量&#xff0c;则不可变&#xff0c;而若指向的区域为变量&#xff0c;那么可以改变
字符串数组数组名定义了之后就不可变&#xff0c;但是数组里的值可变&#xff0c;而定义一个指针&#xff0c;指针变量的值是可变的&#xff0c;但是若其指向的为常量&#xff0c;那么则指针指向的字符串的值不可变
字符串的基本操作
包含在头文件
字符串长度&#xff1a;strlen(s)
返回字符串s的长度&#xff08;s的长度不包括 ’ \\0 &#39;&#xff09;
区别sizeof()&#xff0c;sizeof()计算的为占用的空间&#xff0c;strlen()计算的为字符串长度
字符串比较&#xff1a;strcmp(s1, s2)
若s1和s2 是相同的&#xff0c;则返回0&#xff1b;
若s1 若s1 > s2 则返回值大于0
字符串拷贝&#xff1a;strcpy(s1, s2)
复制字符串s2到字符串s1
复制指定长度字符串&#xff1a;strncpy(s1, s2, n)
将字符串s2中前n个字符串拷贝到s1中
字符串拼接&#xff1a;strcat(s1, s2)
将字符串s2拼接到s1之后
查找字符串&#xff1a;strchr(s1, ch)
指向字符串s1中字符ch的第一次出现的位置
查找字符串&#xff1a;strstr(s1, s2)
指向字符串s1中字符串s2的第一次出现的位置
进行底层操作时&#xff0c;为避免编译器错误发出报错&#xff0c;可添加宏&#xff1a;_CRT_SECURE_NO_WARNINGS
缓冲区溢出问题
举例&#xff1a;在进行字符串拼接的操作时&#xff0c;若拼接长度过大而超出原本的长度&#xff0c;那么就有可能把存储区原本存储的信息改变&#xff0c;造成逻辑的改变
解决方法&#xff1a;养成进行边界判断的习惯&#xff0c;另外也存在更安全的API可供调用&#xff0c;如strcpy()更为安全的版本为strcpy_s()&#xff0c;其他几个字符串操作函数的安全版本均为*_s()&#xff0c;例如strcat_s(s1, size, s2,)&#xff0c;当s2的长度大于size时&#xff0c;那么会报错&#xff0c;无法运行
string简介
#include
using namespace std;
//定义字符串变量
string s;//定义空字符串
string s &#61; "helloworld";//定义并初始化
string s ( "helloworld" );
string s &#61; string( "helloworld" );
字符串相关函数&#xff1a;
获取字符串的长度
s.length()//字符串长度
s.size()//同上
s.capacity()//字符串s所占空间大小
字符串比较&#xff1a;&#61; &#61; ! &#61; > > &#61; <<&#61;
string s1 &#61; "hello", s2 &#61; "world";
cout <<(s1 &#61;&#61; s2) <
字符串的常用操作&#xff1a;
//转换为C风格的字符串
const char *c_str1 &#61; s1.c_str();
//随机访问&#xff08;获取字符串中某个字符&#xff09;&#xff1a;[]
string s &#61; "hello";
cout <//字符串拷贝&#xff1a;&#61;
string s1 &#61; "hello";
string s2 &#61; s1;
//字符串拷贝&#xff1a;&#43;、&#43;&#61;
string s1 &#61; "hello", s2 &#61; "world";
string s3 &#61; s1 &#43; s2;//s3:helloworld
s1 &#43;&#61; s2;//s1:helloworld
总结&#xff1a;string结合了C&#43;&#43;的新特性&#xff0c;使用起来比原始的C风格更安全和方便&#xff0c;对性能要求不是特别高的常见情况可以使用
C&#43;&#43;中内存单元内容与地址
指针本身就是一个变量&#xff0c;其符合变量定义的基本形式&#xff0c;它存储的是值的地址。对类型T&#xff0c;T*是“到T的指针”类型&#xff0c;一个类型为T*的变量能保存一个类型T的对象的地址
通过一个指针访问它所指向的地址的过程称为间接访问或者引用指针&#xff0c;这个用于执行简介访问的操作符是单目运算符*
一个变量有三个信息&#xff1a;
左值与右值
左值&#xff1a;编译器为其单独分配了一块存储空间&#xff0c;可以取其地址的&#xff0c;左值可以放在赋值运算符的左边&#xff08;最常见的情况如函数和数据成员的名字&#xff09;
右值&#xff1a;指数据本身&#xff0c;不能取到其自身地址&#xff0c;右值只能在赋值运算符右边&#xff08;右值是没有标识符&#xff0c;不可以取地址的表达式&#xff0c;一般也称之为临时对象&#xff09;
一般指针数组和指针数组
指针的数组与数组的指针&#xff1a;
访问数组的指针所指向的数组的元素时&#xff0c;如&#xff1a;(*t)[3]表示指针所指向的数组的下标为3的元素
const与指针
char const *pStr1 &#61; "helloworld";
char* const pStr2 &#61; "helloworld";//pStr2不可改
char const* const pStr3 &#61; "helloworld";//pStr3不可改
关于const修饰符的部分&#xff1a;
如pStr1指针所指向的地址的内容不能变&#xff0c;pStr2指针指向的地址不能变&#xff0c;而pStr3二者均不可改变
指向指针的指针
int a &#61; 123;
int* b &#61; &a;
int** c &#61; &b;//**c相当于*b&#xff0c;即得到a的值&#xff08;表达式从里向外逐层求值&#xff09;
//*操作符具有从右向左的结合性
野指针
未初始化和非法指针
//eg&#xff1a;
int* a;//仅定义了一个指针&#xff0c;其所指的区域不确定
*a &#61; 12;
避免方法&#xff1a;用指针进行间接访之前&#xff0c;一定要确保其已经初始化&#xff0c;并被恰当地赋值
NULL指针
一个特殊的指针变量&#xff0c;表示不指向任何东西
int* a &#61; NULL;
NULL指针给出一种方法&#xff0c;来表示特定的指针目前未指向任何东西
注意事项&#xff1a;
杜绝“野”指针
指向“垃圾”内存的指针&#xff0c;if等判断对它们不起作用&#xff0c;因为没有置NULL
三种情况&#xff1a;
注意事项&#xff1a;没有初始化的&#xff0c;不用的或者超出范围的指针将其值置为NULL
&与*操作符
指针类型默认都是四个字节大小&#xff0c;而与其指向的空间的数据类型无关
char ch &#61; &#39;a&#39;;
char* cp &#61; &ch;
//表达式
//*cp
char ch2 &#61; *cp;//作为右值&#xff0c;将a赋给ch2
*cp &#61; &#39;b&#39;;//作为左值&#xff0c;等价于对ch进行操作
//*cp &#43; 1
char ch2 &#61; *cp &#43; 1;//作为右值&#xff0c;将b赋给ch2
*cp &#43; 1 &#61; ...;//作为左值非法
//*(cp &#43; 1)
char ch2 &#61; *(cp &#43; 1);//作为右值&#xff0c;将ch后一个地址的元素的值赋给ch2
*(cp &#43; 1) &#61; &#39;b&#39;;//作为左值&#xff0c;等价于对ch后一个地址的元素进行操作
原始指针的基本运算
&#43;&#43; 与 – 操作符
char* cp2 &#61; &#43;&#43;cp;
/*
mov eax,dword ptr [cp]
add eax,1
mov dword ptr [cp],eax
mov ecx,dword ptr [cp]
mov dword ptr [cp2],ecx
*/
char* cp3 &#61; cp&#43;&#43;;
/*
mov eax,dword ptr [cp]
mov dword ptr [cp3],eax
mov exc,dword ptr [cp]
add ecx,1
mov dword ptr [cp],ecx
*/
// -- 操作同理 add --> sub
*&#43;&#43;p &#61; a;
//相当于对p &#43; 1所指的位置进行赋值操作
*p&#43;&#43; &#61; a;
//相当于对p所指位置进行赋值后p的
关于&#43;&#43;&#43;&#43;&#xff0c;----等运算符
编译器分解程序符号的方法&#xff1a;逐字符读入&#xff0c;若该字符可能组成一个符号&#xff0c;那么读入下一个字符&#xff0c;知道读入的字符不能在组成一个有意义的符号&#xff08;贪心法&#xff09;
int a &#61; 1, b &#61; 2, c, d;
c &#61; a &#43;&#43;&#43; b; // --> (a &#43;&#43;) &#43; b
d &#61; a &#43;&#43;&#43;&#43; b; //error
&#43;&#43;*&#43;&#43;p; // --> &#43;&#43;(*(&#43;&#43;p))
#include "stdafx.h"
#include
int a &#61; 0; //(GVAR)全局初始化区
int* p1; //(bss)全局未初始化区
int main() //(text)代码区
int b&#61;1; //(stack)栈区变量
char s[] &#61; "abc"; //(stack)栈区变量
int*p2&#61;NULL; //(stack)栈区变量
char *p3 &#61; "123456"; //123456\\0在常量区, p3在(stack)栈区
static int c &#61; 0; //(GVAR)全局(静态)初始化区
p1 &#61; new int(10); //(heap)堆区变量
p2 &#61; new int(20); //(heap)堆区变量
char* p4 &#61; new char[7]; //(heap)堆区变量
strcpy_s(p4, 7, "123456"); //(text)代码区
//(text)代码区
if (p1 !&#61; NULL)
delete p1;
p1 &#61; NULL;
if (p2 !&#61; NULL)
delete p2;
p2 &#61; NULL;
if (p4 !&#61; NULL)
delete[ ] p4;
p4 &#61; NULL;
//(text)代码区
return 0; //(text)代码区
动态分配资源&#xff1a;堆&#xff08;heap&#xff09;
程序通常需要涉及三个内存管理器的操作&#xff1a;
回收策略需要实现性能、实时性、额外开销等方面的平衡&#xff0c;很难有统一和高效的做法。
C&#43;&#43;做了1&#xff0c;2&#xff1b;而Java做了1&#xff0c;3。
资源管理方案&#xff1a;RAII(Resource Acquisition Is Initialization)
RAII依托栈和析构函数&#xff0c;来对所有的资源&#xff1a;包括堆内存在内进行管理。对RAII的使用&#xff0c;使得C&#43;&#43;不需要类似于Java那样的垃圾收集方法,也能有效地对内存进行管理。RAII 的存在&#xff0c;也是垃圾收集虽然理论上可以在C&#43;&#43;使用&#xff0c;但从来没有真正流行过的主要原因。
RAII比较成熟的智能指针代表&#xff1a;
std::auto_ptr
boost::shared_ptr
栈和堆中的变量对比
全局静态存储区和常量存储区的变量对比
什么是内存泄漏问题&#xff08;memory leak&#xff09;
指程序中己动态分配的堆内存由于某种原因程序未释放或无法释&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。
内存泄漏发生原因和排查方式
注意delete 和delete[] 的区别。
使用指针是非常危险的行为&#xff0c;可能存在空指针&#xff0c;野指针问题&#xff0c;并可能造成内存泄漏问题。
可指针又非常的高效&#xff0c;所以我们希望以更安全的方式来使用指针。
两种方案&#xff1a;
C&#43; &#43;中推出了四种常用的智能指针:
unique_ ptr、 shared ptr、weak_ ptr 和C&#43;&#43; 11中已经废弃(deprecated)的auto_ ptr,在C&#43;&#43; 17中被正式删除。
auto_ptr
由new expression获得对象&#xff0c;在auto_ ptr 对象销毁时&#xff0c;他所管理的对象也会
自动被delete掉。
所有权转移:不小心把它传递给另外的智能指针&#xff0c;原来的指针就不再拥有这个对象了。在拷贝/赋值过程中&#xff0c;会直接剥夺指针对原对象对内存的控制权&#xff0c;转交给新对象&#xff0c;然后再将原对象指针置为nullptr。
演示代码&#xff1a;
#include
#include <iostream>
#include
using namespace std;
int main()
// 确定auto_ptr失效的范围
// 对int使用
auto_ptr
cout <<*pI <
// auto_ptr原理&#xff1a;在拷贝 / 赋值过程中&#xff0c;直接剥夺原对象对内存的控制权&#xff0c;转交给新对象&#xff0c;
// 然后再将原对象指针置为nullptr&#xff08;早期&#xff1a;NULL&#xff09;。这种做法也叫管理权转移。
// 他的缺点不言而喻&#xff0c;当我们再次去访问原对象时&#xff0c;程序就会报错&#xff0c;所以auto_ptr可以说实现的不好&#xff0c;
// 很多企业在其库内也是要求不准使用auto_ptr。
auto_ptr
auto_ptr
auto_ptr
auto_ptr
auto_ptr
auto_ptr
;
cout <<"There are some computer languages here first time: \\n";
for (int i &#61; 0; i <5; &#43;&#43;i)
cout <<*languages[i] <
auto_ptr
pC &#61; languages[2]; // languges[2] loses ownership. 将所有权从languges[2]转让给pC&#xff0c;
//此时languges[2]不再引用该字符串从而变成空指针
cout <<"There are some computer languages here second time: \\n";
for (int i &#61; 0; i <2; &#43;&#43;i)
cout <<*languages[i] <
cout <<"The winner is " <<*pC <
//for (int i &#61; 0; i <5; &#43;&#43;i)
//
// cout <<*languages[i] <
return 0;
unique_ptr
unique_ptr是专属所有权&#xff0c;所以unique_ptr管理的内存&#xff0c;只能被一个对象持有&#xff0c;不支持复制和赋值。
移动语义: unique_ptr禁止了拷贝语义&#xff0c;但有时我们也需要能够转移所有权&#xff0c;于是提供了移动语义&#xff0c;即可以使用std::move()进行控制所有权的转移。
#include
#include
using namespace std;
int main()
// 在这个范围之外&#xff0c;unique_ptr被释放
auto i &#61; unique_ptr
cout <<*i <
// unique_ptr
auto w &#61; std::make_unique
cout <<*(w.get()) <
// 因为复制从语义上来说&#xff0c;两个对象将共享同一块内存。
// unique_ptr 只支持移动语义, 即如下
auto w2 &#61; std::move(w); // w2 获得内存所有权&#xff0c;w 此时等于 nullptr
cout <<((w.get() !&#61; nullptr) ? (*w.get()) : -1) <
shared_ptr
shared_ptr通过一个引用计数共享一个对象.
shared_ ptr 是为了解决auto_ ptr在对象所有权上的局限性&#xff0c;在使用引用计数的机制上提供了可以共享所有权的智能指针&#xff0c;当然这需要额外的开销。
通过引用计数对引用个数进行记录&#xff0c;当引用计数为0时&#xff0c;该对象没有被使用&#xff0c;可以进行析构。
#include
#include
using namespace std;
int main()
// shared_ptr
//shared_ptr 代表的是共享所有权&#xff0c;即多个 shared_ptr 可以共享同一块内存。
auto wA &#61; shared_ptr
auto wA2 &#61; wA;
cout <<((wA2.get() !&#61; nullptr) ? (*wA2.get()) : -1) <
cout <
// 引用计数会 &#43; 1。当一个 shared_ptr 离开作用域时&#xff0c;引用计数会 - 1。
// 当引用计数为 0 的时候&#xff0c;则 delete 内存。
// move 语法
auto wAA &#61; std::make_shared
auto wAA2 &#61; std::move(wAA); // 此时 wAA 等于 nullptr&#xff0c;wAA2.use_count() 等于 1
cout <<((wAA.get() !&#61; nullptr) ? (*wAA.get()) : -1) <
//而 wAA2 获得了对象所有权&#xff0c;但因为此时 wAA 已不再持有对象&#xff0c;因此 wAA2 的引用计数为 1。
return 0;
带来的问题&#xff1a;循环引用
循环引用会导致堆里的内存无法正常回收&#xff0c;造成内存泄漏。
weak_ptr
weak_ ptr 被设计为与shared_ ptr 共同工作&#xff0c;用- -种观察者模式工作。
作用是协助shared_ptr 工作&#xff0c;可获得资源的观测权,像旁观者那样观测资源的使用情况。
观察者意味着weak_ ptr只对shared_ ptr 进行引用&#xff0c;而不改变其引用计数&#xff0c;当被观察的shared_ ptr 失效后&#xff0c;相应的weak_ ptr 也相应失效。
#include
#include
#include
using namespace std;
struct B;
struct A
shared_ptr pb;
~A()
cout <<"~A()" <
;
struct B
shared_ptr pa;
~B()
cout <<"~B()" <
;
// pa 和 pb 存在着循环引用&#xff0c;根据 shared_ptr 引用计数的原理&#xff0c;pa 和 pb 都无法被正常的释放。
// weak_ptr 是为了解决 shared_ptr 双向引用的问题。
struct BW;
struct AW
shared_ptr
~AW()
cout <<"~AW()" <
;
struct BW
weak_ptr
~BW()
cout <<"~BW()" <
;
void Test()
cout <<"Test shared_ptr and shared_ptr: " <
shared_ptr tB(new B()); // 1
cout <
tB->pa &#61; tA;
cout <
cout <<"Test weak_ptr and shared_ptr: " <
shared_ptr
cout <
tB->pa &#61; tA;
cout <
Test();
Test2();
return 0;
引用&#xff1a;一种特殊的指针&#xff0c;不允许修改的指针。
使用指针有哪些坑&#xff1a;
使用引用&#xff0c;则可以&#xff1a;
引用的基本使用&#xff1a;可以认为是指定变量的别名&#xff0c;使用时可以认为是变量本身
示例&#xff1a;
int x &#61; 1, x2 &#61; 3;
int& rx &#61; x;
rx &#61; 2;
cout <
cout <
两个问题
有了指针为什么还需要引用?
Bjarne Stroustrup的解释&#xff1a;为了支持函数运算符重载&#xff1b;
有了引用为什么还需要指针?
Bjarne Stroustrup的解释&#xff1a;为了兼容C语言。
补充&#xff1a;关于函数传递参数类型的说明
对内置基础类型(如int,double等)而言&#xff1a;
在函数中传递时pass by value更高效&#xff1b;
对OO面向对象中自定义类型而言&#xff1a;
在函数中传递时pass by reference to const更高效。